home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998 August: Tool Chest / Dev.CD Aug 98 TC.toast / Sample Code / Networking / OTStreamLogViewer1.0b1 / OTStreamLogViewer.c < prev    next >
Encoding:
C/C++ Source or Header  |  1998-03-19  |  56.9 KB  |  1,946 lines  |  [TEXT/CWIE]

  1. /*
  2.     File:        StreamLogWatcher.c
  3.  
  4.     Contains:    A program to display information logged to the STREAMS log module.
  5.  
  6.     Written by:    Quinn "The Eskimo!"
  7.  
  8.     Copyright:    Â© 1998 by Apple Computer, Inc., all rights reserved.
  9.  
  10.     Change History (most recent first):
  11.  
  12.     You may incorporate this sample code into your applications without
  13.     restriction, though the sample code has been provided "AS IS" and the
  14.     responsibility for its operation is 100% yours.  However, what you are
  15.     not permitted to do is to redistribute the source as "DSC Sample Code"
  16.     after having made changes. If you're going to re-distribute the source,
  17.     we require that you make it clear in the source that the code was
  18.     descended from Apple Sample Code, but that you've made changes.
  19. */
  20.  
  21. #define qDebug 1
  22.  
  23. /////////////////////////////////////////////////////////////////////
  24. // Standard C stuff.
  25.  
  26. #import <stdio.h>
  27.  
  28. /////////////////////////////////////////////////////////////////////
  29. // Pick up standard OT APIs.
  30.  
  31. #import <OpenTransport.h>
  32.  
  33. /////////////////////////////////////////////////////////////////////
  34. // Pick up the main ASLM APIs.
  35.  
  36. #if __MC68K__
  37.  
  38.     // ^%$**(#)%( ASLM defines it's own prototype for strcpy,
  39.     // even though it includes "string.h".  This conflicts with the way
  40.     // CodeWarrior does inline strcpy in MSL in 68K.  Argh!  So
  41.     // I've done an evil C pre-processor hacks to get around this problem
  42.     // just for the sake of getting this project to compile out of the box.
  43.     // The real solution is to edit out the extra prototype for strcpy
  44.     // in your copy of "LibraryManager".
  45.  
  46.     #import <string.h>
  47.     
  48.     #undef strcpy
  49.     #define strcpy BogusStrCpy
  50.  
  51.     #import <LibraryManager.h>
  52.  
  53.     #undef strcpy
  54.     #define strcpy(dst, src)    __strcpy(dst, src)
  55.  
  56. #else
  57.     #import <LibraryManager.h>
  58. #endif
  59.  
  60. /////////////////////////////////////////////////////////////////////
  61. // Pick up the low-level OT APIs.
  62.  
  63. #import <OTDebug.h>
  64.  
  65. /////////////////////////////////////////////////////////////////////
  66. // Pick up standard toolbox APIs.
  67.  
  68. #import <QuickDraw.h>
  69. #import <Fonts.h>
  70. #import <Windows.h>
  71. #import <Menus.h>
  72. #import <TextEdit.h>
  73. #import <Dialogs.h>
  74. #import <Memory.h>
  75. #import <Resources.h>
  76. #import <TextUtils.h>
  77. #import <Scrap.h>
  78. #import <Devices.h>
  79. #import <CodeFragments.h>
  80. #import <Gestalt.h>
  81. #import <Processes.h>
  82.  
  83. /////////////////////////////////////////////////////////////////////
  84. // Pick up less-standard toolbox APIs.
  85.  
  86. #import "InternetConfig.h"
  87.  
  88. /////////////////////////////////////////////////////////////////////
  89. // Lots of useful subroutines from the Internet Config libraries.
  90.  
  91. #import "ICDialogs.h"
  92. #import "ICMiscSubs.h"
  93. #import "ICCommonSubs.h"
  94.  
  95. /////////////////////////////////////////////////////////////////////
  96. // Pick up our resource definitions.
  97.  
  98. #import "StreamLogResources.h"
  99.  
  100. /////////////////////////////////////////////////////////////////////
  101. // Pick up the file logging stuff.
  102.  
  103. #import "FileLogging.h"
  104.  
  105. /////////////////////////////////////////////////////////////////////
  106. // Pick up the log engine prototypes.
  107.  
  108. #import "LogEngine.h"
  109.  
  110. /////////////////////////////////////////////////////////////////////
  111. // OTDebugStr is not defined in any OT header files, but it is
  112. // exported by the libraries, so we define the prototype here.
  113.  
  114. extern pascal void OTDebugStr(const char* str);
  115.  
  116. /////////////////////////////////////////////////////////////////////
  117. #pragma mark **** Applications Globals *****
  118.  
  119. enum {
  120.     kCreator = 'SlVw'
  121. };
  122.  
  123. static Boolean gQuitNow;
  124.     // Set to true to drop out of the main event loop.
  125.     
  126. static DialogPtr gMainWindow = nil;
  127.     // The main window.  Pretty much the only window at
  128.     // the moment.
  129.     
  130. static ListHandle gLogList = nil;
  131.     // The List Manager list in the main window.
  132.  
  133. static ListDefUPP gListDefProcUPP;
  134.     // A UPP for the list definition proc we use
  135.     // for this list.  We have a dummy LDEF whose
  136.     // resource ID we supply to LNew.  That LDEF just
  137.     // calls through the list's refCon (assuming it isn't
  138.     // nil).  We poke this UPP into that refCon, and hence
  139.     // the LDEF calls us.  This allows for source
  140.     // level debugging of the LDEF without messing around
  141.     // with self-modifying code.
  142.  
  143. static UserItemUPP gListUserItemUpdateUPP;
  144.     // A UPP for the user item in gMainWindow that contains
  145.     // the gLogList.  This routine simply calls LUpdate
  146.     // (oh, and also draws the frame for the list).
  147.  
  148. static Rect gMainWindowGrowBounds;
  149.     // Bounds which we pass to GrowWindow to constrain how
  150.     // much the window can grow or shrink.  The topLeft
  151.     // co-ordinate in the minimum size (which we get from
  152.     // the window's portRect just after we create it)
  153.     // and the botRight are derived from screenBits.bounds
  154.     // (which Window Manager special cases to allow windows
  155.     // to grow as large as possible on the screen(s)).
  156.     
  157. static Boolean gScrollWhileLogging = true;
  158.     // This value is a mirror of the state of the menu
  159.     // item.  We mirror it to make it quicker to fetch while
  160.     // we're logging.
  161.     
  162. static SInt16 gModuleID;                        // -1 => log all modules
  163. static SInt16 gStreamID;                        // -1 => log all streams
  164.     // These are the logging filters.  This only take effect
  165.     // if the All Traces radio button is /not/ on.  The values
  166.     // are set by means of a modal dialog.
  167.  
  168. static void DirtyPreferences(void);
  169.     // forward
  170. static void PreferencesIdle(void);
  171.     // forward
  172.  
  173. /////////////////////////////////////////////////////////////////////
  174. #pragma mark **** Apple Event Handlers *****
  175.  
  176. static pascal OSErr AEOpenApplicationHandler(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
  177. {
  178.     #pragma unused(reply)
  179.     #pragma unused(handlerRefcon)
  180.     OSStatus err;
  181.     
  182.     err = AEGotRequiredParams(theAppleEvent);
  183.     return err;
  184. }
  185.  
  186. static pascal OSErr AEOpenDocumentsHandler(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
  187. {
  188.     #pragma unused(reply)
  189.     #pragma unused(handlerRefcon)
  190.     OSStatus err;
  191.     
  192.     err = AEGotRequiredParams(theAppleEvent);
  193.     return err;
  194. }
  195.  
  196. static pascal OSErr AEQuitApplicationHandler(const AppleEvent *theAppleEvent, AppleEvent *reply, long handlerRefcon)
  197. {
  198.     #pragma unused(reply)
  199.     #pragma unused(handlerRefcon)
  200.     OSStatus err;
  201.     
  202.     err = AEGotRequiredParams(theAppleEvent);
  203.     if (err == noErr) {
  204.         gQuitNow = true;
  205.     }
  206.     return err;
  207. }
  208.  
  209. /////////////////////////////////////////////////////////////////////
  210. #pragma mark **** List Logging Stuff *****
  211.  
  212. enum {
  213.  
  214.     // kMaxListRows is the maximum number of rows we can have in our
  215.     // scrolling list.  List Manager imposes an overall limit of 32K.
  216.     // It also imposes a 32K limit on the amount of data in the list.
  217.     // We use 4 bytes per row of data, which would imply this limit
  218.     // should be 8K.  5000 is just to keep it within reasonable bounds.
  219.     
  220.     kMaxListRows = 5000,
  221.     
  222.     // When the list exceeds the above limit, we delete some rows from
  223.     // the front of the list.  We do this in chunks for efficiency's
  224.     // sake.  This is the size of the chunk we delete.
  225.     
  226.     kNumberOfRowsToDelete = 100
  227. };
  228.  
  229. static LogEntryPtr ListGetLogEntryForRow(SInt16 rowNumber)
  230.     // Given a row number (zero based), this routine returns
  231.     // the log entry for that row.  if there is no log entry
  232.     // (which can happen if the routine is called by the LDEF
  233.     // as the row is added), it returns nil.
  234. {
  235.     Cell thisCell;
  236.     LogEntryPtr result;
  237.     SInt16 dataSize;
  238.     
  239.     thisCell.h = 0;
  240.     thisCell.v = rowNumber;
  241.     dataSize = sizeof(result);
  242.     LGetCell(&result, &dataSize, thisCell, gLogList);
  243.     if ( dataSize != sizeof(result) ) {
  244.         result = nil;
  245.     }
  246.     return result;
  247. }
  248.  
  249. static void ListSetLogEntryForRow(SInt16 rowNumber, LogEntryPtr rowEntry)
  250.     // This routine sets the data for the given rowNumber (zero based)
  251.     // to rowEntry, to later be retrieved by ListGetLogEntryForRow.
  252. {
  253.     Cell thisCell;
  254.     
  255.     thisCell.h = 0;
  256.     thisCell.v = rowNumber;
  257.     LSetCell(&rowEntry, sizeof(rowEntry), thisCell, gLogList);
  258. }
  259.  
  260. static void FlagsToShortString(char flags, Str255 flagStr)
  261.     // This routine sets flagStr to an 8 character string
  262.     // that represents flags in a very compact form, suitable
  263.     // for drawing by the LDEF.
  264. {
  265.     OTMemset(&flagStr[1], '-', 8);
  266.     flagStr[0] = 8;
  267.     
  268.     if ((flags & SL_TRACE) != 0) {
  269.         flagStr[1] = 'T';
  270.     }
  271.     if ((flags & SL_ERROR) != 0) {
  272.         flagStr[2] = 'E';
  273.     }
  274.     if ((flags & SL_CONSOLE) != 0) {
  275.         flagStr[3] = 'C';
  276.     }
  277.     
  278.     // flagStr[4] is blank
  279.     
  280.     if ((flags & SL_FATAL) != 0) {
  281.         flagStr[5] = 'F';
  282.     }
  283.     if ((flags & SL_NOTIFY) != 0) {
  284.         flagStr[6] = 'N';
  285.     }
  286.     if ((flags & SL_WARN) != 0) {
  287.         flagStr[7] = 'W';
  288.     }
  289.     if ((flags & SL_NOTE) != 0) {
  290.         flagStr[8] = 'N';
  291.     }
  292. }
  293.  
  294. // These constants define the start (and hence the width)
  295. // of each of the fields in our LDEF.
  296.  
  297. enum {
  298.     kFieldStart0 = 4,
  299.     kFieldStart1 = kFieldStart0 + 40,
  300.     kFieldStart2 = kFieldStart1 + 60,
  301.     kFieldStart3 = kFieldStart2 + 60,
  302.     kFieldStart4 = kFieldStart3 + 60,
  303.     kFieldStart5 = kFieldStart4 + 40,
  304.     kFieldStart6 = kFieldStart5 + 40,
  305.     kFieldStart7 = kFieldStart6 + 0
  306. };
  307.  
  308. static void ListDefProcDraw(Rect *theCellRect, SInt16 rowNumber)
  309.     // This routine is called by the LDEF in response to an
  310.     // lDraw message.  The basic idea is to get the log entry
  311.     // for the given row and then use the given rectangle to
  312.     // render the data in the entry.
  313. {
  314.     LogEntryPtr thisEntry;
  315.     Str255 tmpStr;
  316.     UInt32 strLen;
  317.  
  318.     thisEntry = ListGetLogEntryForRow(rowNumber);
  319.     OTAssert("ListDefProc: No data for cell", thisEntry != nil);
  320.     if (thisEntry != nil) {
  321.  
  322.         NumToString(thisEntry->fLogHeader.seq_no, tmpStr);
  323.         MoveTo(theCellRect->left + kFieldStart0, theCellRect->bottom - 4);
  324.         DrawString(tmpStr);
  325.  
  326.         MoveTo(theCellRect->left + kFieldStart1, theCellRect->bottom - 4);
  327.         FlagsToShortString(thisEntry->fLogHeader.flags, tmpStr);
  328.         DrawString(tmpStr);
  329.  
  330.         MoveTo(theCellRect->left + kFieldStart2, theCellRect->bottom - 4);
  331.         DateString(thisEntry->fLogHeader.ttime, shortDate, tmpStr, nil);
  332.         DrawString(tmpStr);
  333.  
  334.         TimeString(thisEntry->fLogHeader.ttime, true, tmpStr, nil);
  335.         MoveTo(theCellRect->left + kFieldStart3, theCellRect->bottom - 4);
  336.         DrawString(tmpStr);
  337.         
  338.         NumToString(thisEntry->fLogHeader.mid, tmpStr);
  339.         MoveTo(theCellRect->left + kFieldStart4, theCellRect->bottom - 4);
  340.         DrawString(tmpStr);
  341.  
  342.         NumToString(thisEntry->fLogHeader.sid, tmpStr);
  343.         MoveTo(theCellRect->left + kFieldStart5, theCellRect->bottom - 4);
  344.         DrawString(tmpStr);
  345.  
  346.         // We clip strLen at 255 to avoid attempting to draw more than
  347.         // a 255 character string (which won't work 'cause it's a Pascal
  348.         // string).  We could have used DrawText, but 255 characters is
  349.         // enough for me.  If you need more, look at the file log.
  350.         
  351.         strLen = thisEntry->fTextLength;
  352.         if ( strLen > 255 ) {
  353.             strLen = 255;
  354.         }
  355.         BlockMoveData((char *) thisEntry + sizeof(LogEntry), &tmpStr[1], strLen);
  356.         tmpStr[0] = strLen;
  357.         MoveTo(theCellRect->left + kFieldStart6, theCellRect->bottom - 4);
  358.         DrawString(tmpStr);
  359.     }
  360. }
  361.  
  362. static pascal void ListDefProc(SInt16 message, Boolean drawSelected,
  363.                                 Rect *theCellRect, Cell theCell, 
  364.                                 SInt16 dataOffset, SInt16 dataLen, 
  365.                                 ListRef listH)
  366.     // Our list definition proc (LDEF).  This routine is called
  367.     // back by the List Manager when it wants to draw (or highlight)
  368.     // a cell in the list in our main window.  The implementation
  369.     // basically dispatches based on message.  Of cours, there's only
  370.     // one basic action, so this is just a glorified wrapper for
  371.     // ListDefProcDraw.
  372. {
  373.     #pragma unused(dataOffset)
  374.     #pragma unused(dataLen)
  375.  
  376.     #if qDebug
  377.         OTAssert("ListDefProc: This LDEF only works for gLogList", listH == gLogList);
  378.     #else
  379.         #pragma unused(listH)
  380.     #endif
  381.     
  382.     switch (message) {
  383.         case lInitMsg:
  384.             // do nothing
  385.             break;
  386.         case lDrawMsg:
  387.         case lHiliteMsg:
  388.             EraseRect(theCellRect);
  389.             ListDefProcDraw(theCellRect, theCell.v);
  390.             if (drawSelected) {
  391.                 MagicMarkerMode();
  392.                 InvertRect(theCellRect);
  393.             }
  394.             break;
  395.         case lCloseMsg:
  396.             // do nothing
  397.             break;
  398.         default:
  399.             OTDebugStr("ListDefProc: Unrecognised message");
  400.             break;
  401.     }
  402. }
  403.  
  404. static pascal void ListUserItemUpdateProc(WindowPtr dlg, short item)
  405.     // A Dialog Manager user item update proc.  This routine
  406.     // is called by the Dialog Manager when it wants to draw
  407.     // the list user item.  We respond by drawing the list frame
  408.     // and then calling through to List Manager to draw the list itself.
  409. {
  410.     Rect itemRect;
  411.     
  412.     // This erase is critical, because List Manager is not smart
  413.     // enough to erase the area below the defined cells when
  414.     // the defined cells don't take up enough space to occupy the
  415.     // entire viewable area.
  416.     
  417.     GetDItemRect(dlg, item, &itemRect);
  418.     EraseRect(&itemRect);
  419.     
  420.     // Draw the frame.
  421.     
  422.     InsetRect(&itemRect, -1, -1);
  423.     FrameRect(&itemRect);
  424.     
  425.     // Call List Manager to draw the list.
  426.     
  427.     LUpdate(dlg->clipRgn, gLogList);
  428. }
  429.  
  430. static void RecordLogEntryToList(LogEntryPtr thisEntry)
  431.     // This routine is called at idle time when new list entries
  432.     // are noticed.  The basic idea is to append the new entries
  433.     // as rows at the end of the list.  We also have to deal with
  434.     // keeping the list size within bounds and scrolling the new
  435.     // items into view if the user asked to.
  436. {
  437.     SInt16 newRow;
  438.     SInt32 i;
  439.     LogEntryPtr anEntry;
  440.     Cell tmpCell;
  441.     
  442.     OTAssert("RecordLogEntryToList: gLogList is nil", gLogList != nil);
  443.     
  444.     // First check to see whether we have too many rows in the list.  If so,
  445.     // delete the first kNumberOfRowsToDelete rows, making sure we release
  446.     // the data associated with deleted rows.
  447.     
  448.     if ( ((**gLogList).dataBounds.bottom - (**gLogList).dataBounds.top) > kMaxListRows) {
  449.         for (i = 0; i < kNumberOfRowsToDelete; i++) {
  450.             anEntry = ListGetLogEntryForRow(i);
  451.             OTAssert("RecordLogEntryToList: No list data", anEntry != nil);
  452.             ReleaseLogEntry(anEntry);
  453.         }
  454.         LDelRow(kNumberOfRowsToDelete, 0, gLogList);
  455.     }
  456.     
  457.     // Now add the row, and set the data for the new row.
  458.     // Make sure we call RetainLogEntry to denote the extra
  459.     // reference to this data.
  460.     
  461.     LSetDrawingMode(false, gLogList);
  462.     
  463.     newRow = LAddRow(1, 32767, gLogList);
  464.     RetainLogEntry(thisEntry);
  465.     LSetDrawingMode(true, gLogList);
  466.     ListSetLogEntryForRow(newRow, thisEntry);
  467.  
  468.     tmpCell.h = 0;
  469.     tmpCell.v = newRow;
  470.  
  471.     // Finally, scroll the list to show the new item if the user
  472.     // asked us to.
  473.         
  474.     if ( gScrollWhileLogging ) {
  475.         LScroll(0, newRow, gLogList);
  476.     }
  477. }
  478.  
  479. /////////////////////////////////////////////////////////////////////
  480. #pragma mark **** General User Interface Stuff *****
  481.  
  482. static void SizeMainWindow(SInt16 newWidth, SInt16 newHeight)
  483.     // This routine is used to resize the main window,
  484.     // making sure that the list in the window is also resized
  485.     // to match.
  486. {
  487.     Rect itemRect;
  488.     
  489.     // Round the height to the closest 16 pixel boundary.
  490.     // We need to make sure the list stays a multiple of
  491.     // 16 pixels high (otherwise List Manager behaves
  492.     // suckily), and we do this by making sure the window
  493.     // is always a multiple of 16 pixels high.
  494.     
  495.     newHeight = (newHeight + 8) / 16 * 16 - 1;
  496.     
  497.     // Resize the window.
  498.     
  499.     SizeWindow(gMainWindow, newWidth, newHeight, true);
  500.     
  501.     // Now sync up the dialog item and the list view rect
  502.     // with the new window size.  This took some time to get
  503.     // right.
  504.     
  505.     GetDItemRect(gMainWindow, ditLogEntryList, &itemRect);
  506.     itemRect.right = gMainWindow->portRect.right;
  507.     itemRect.bottom = gMainWindow->portRect.bottom;
  508.     SetDItemRect(gMainWindow, ditLogEntryList, &itemRect);
  509.     itemRect.right -= 15;
  510.     itemRect.bottom -= 15;
  511.     LSize(itemRect.right - itemRect.left,
  512.             itemRect.bottom - itemRect.top,
  513.             gLogList);
  514. }
  515.  
  516. static void SetMainWindowVisibility(Boolean isVisible)
  517.     // We use this routine to show and hide the main
  518.     // window, making sure that all the UI elements that
  519.     // depend on this state are kept in sync.
  520. {
  521.     Str255 menuItemText;
  522.     
  523.     OTAssert("SetMainWindowVisibility: No list handle", gLogList != nil);
  524.  
  525.     if (isVisible) {
  526.         ShowWindow(gMainWindow);
  527.         
  528.         if ( ! TitleBarOnScreen(gMainWindow) ) {
  529.             MoveWindow(gMainWindow, 100, 100, false);
  530.         }
  531.         
  532.         GetIndString(menuItemText, rMiscStrings, strHideLogWindow);
  533.     } else {
  534.         HideWindow(gMainWindow);
  535.         GetIndString(menuItemText, rMiscStrings, strShowLogWindow);
  536.     }
  537.     SetMenuItemText( GetMenuHandle(mFile), iShowHideLogWindow, menuItemText);
  538. }
  539.  
  540. static void SetScrollWhileLogging(Boolean scrollWhileLogging)
  541.     // We use this routine to set the scroll while logging state,
  542.     // keeping the UI in sync.
  543. {
  544.     gScrollWhileLogging = scrollWhileLogging;
  545.     CheckItem( GetMenuHandle(mFile), iScrollWhileLogging, gScrollWhileLogging);
  546. }
  547.  
  548. static void DisplayError(OSStatus errNum)
  549. {
  550.     Str255 becauseString;
  551.     Str255 errNumStr;
  552.     
  553.     if (errNum != noErr && errNum != userCanceledErr) {
  554.         NumToString(errNum, errNumStr);
  555.         NewLookupErrorC(rErrorTable, errNum, becauseString);
  556.         ParamText(becauseString, errNumStr, "\p", "\p");
  557.         (void) StopAlert(rErrorAlert, gOKModalFilterUPP);
  558.     }
  559. }
  560.  
  561. static void UpdateStartStopUI(void)
  562.     // This routine is used to sync up the enabled state of
  563.     // the start and stop user interface to the current
  564.     // logging state.
  565. {
  566.     Str255 tmpStr;
  567.     
  568.     SetDControlEnable(gMainWindow, ditStop, LoggingActive());
  569.     SetDControlEnable(gMainWindow, ditStart, ! LoggingActive());
  570.     if ( LoggingActive() ) {
  571.         GetIndString(tmpStr, rMiscStrings, strStopLogging);
  572.     } else {
  573.         GetIndString(tmpStr, rMiscStrings, strStopLogging);
  574.     }
  575.     SetMenuItemText(GetMenuHandle(mFile), iStartStopLogging, tmpStr);
  576. }
  577.  
  578. static void UIStartLogging(void)
  579.     // This routine handles the user clicking on the Start
  580.     // logging button (or the equivalent menu command).  It
  581.     // gathers the logging parameters from the dialog items
  582.     // and then calls the logging back end.
  583. {
  584.     OSStatus err;
  585.     UInt32 traceInfoCount;
  586.     struct trace_ids *traceInfo;
  587.     struct trace_ids traceInfoBuffer;
  588.  
  589.     // Gather the logging parameters from the dialog.
  590.     
  591.     traceInfoCount = 0;
  592.     traceInfo = nil;
  593.     if ( GetDControlBoolean(gMainWindow, ditLogTraces) ) {
  594.         traceInfoCount = 1;
  595.         traceInfo = &traceInfoBuffer;
  596.         
  597.         if ( GetDControlBoolean(gMainWindow, ditAllTraces) ) {
  598.             traceInfoBuffer.ti_mid = -1;
  599.             traceInfoBuffer.ti_sid = -1;
  600.         } else {
  601.             traceInfoBuffer.ti_mid = gModuleID;
  602.             traceInfoBuffer.ti_sid = gStreamID;
  603.         }
  604.         if ( GetDControlValue(gMainWindow, ditTraceLevel) == iAll ) {
  605.             traceInfoBuffer.ti_level = -1;
  606.         } else {
  607.             traceInfoBuffer.ti_level = GetDControlValue(gMainWindow, ditTraceLevel) - iLevelOffset;
  608.         }
  609.     }
  610.     
  611.     // If we're supposed to be logging to a file, start the
  612.     // file logging module.
  613.     
  614.     err = noErr;
  615.     if ( GetDControlBoolean(gMainWindow, ditLogToFile) ) {
  616.         err = StartFileLogging();
  617.     }
  618.     
  619.     // Start the back end with the parameters we've already
  620.     // worked out.
  621.     
  622.     if (err == noErr) {
  623.         err = StartLogging(GetDControlBoolean(gMainWindow, ditLogErrors), traceInfoCount, traceInfo);
  624.     }
  625.     
  626.     // Update the user interface to reflect the change
  627.     // of logging state.
  628.     
  629.     if ( err == noErr ) {
  630.         UpdateStartStopUI();
  631.     }
  632.     
  633.     // Clean up.
  634.     
  635.     if (err != noErr) {
  636.         if ( FileLoggingActive() ) {
  637.             StopFileLogging();
  638.         }
  639.     }
  640.     
  641.     DisplayError(err);
  642. }
  643.  
  644. static void DoIdle(void);
  645.     // forward
  646.  
  647. static void UIStopLogging(void)
  648.     // This routine is called in response to the user
  649.     // clicking on the Stop logging button (or the equivalent
  650.     // menu command).  It basically calls through to the back
  651.     // end to stop the logging process, then flushouts out
  652.     // any remaining log entries to the log list and file,
  653.     // then shuts down file logging if it was started up.
  654. {
  655.     StopLogging();
  656.     
  657.     // Calling DoIdle will flush out any log entries that remain
  658.     // in the LIFO, which is important to do before we close the
  659.     // file.
  660.     
  661.     DoIdle();
  662.     
  663.     if ( FileLoggingActive() ) {
  664.         StopFileLogging();
  665.     }
  666.     UpdateStartStopUI();
  667. }
  668.  
  669. static Boolean UIConfirmAndStop(void)
  670.     // A generic routine which we call whenever we have to
  671.     // perform some action that will change a logging parameter.
  672.     // We don't want the current logging parameters to be out
  673.     // of sync with the user interface, and I'm too lazy to
  674.     // support adjusting the parameters dynamically, so instead
  675.     // we stop logging whenever the user changes a logging parameter.
  676.     // This routine puts up an alert asking whether that's OK,
  677.     // and then stops logging if it is.  It returns true if
  678.     // it's OK to perform a change in logging parameters (either
  679.     // because logging was off, or because we just turned it off).
  680. {
  681.     Boolean result;
  682.     
  683.     result = false;
  684.     if ( LoggingActive() ) {
  685.         if ( CautionAlert(rConfirmStopAlert, gOKCancelModalFilter) == ditOK ) {
  686.             UIStopLogging();
  687.             result = true;
  688.         }
  689.     } else {
  690.         result = true;
  691.     }
  692.     return result;
  693. }
  694.  
  695. static void UIPoseFilterDialog(void)
  696.     // This routine puts up the dialog that lets the user
  697.     // enter trace filtering parameters.  It populates the dialog
  698.     // based on gModuleID and gStreamID, and if the user clicks
  699.     // OK it writes back to those globals.
  700. {
  701.     DialogPtr dlg;
  702.     Str255 tmpStr;
  703.     SInt32 tmpLong;
  704.     SInt16 hitItem;
  705.     
  706.     // Create and prepare the dialog.
  707.     
  708.     dlg = GetNewDialog(rFilterDialog, nil, (WindowPtr) -1);
  709.     if (dlg != nil) {
  710.  
  711.         SetupDefaultButtonUserItem(dlg, ditOK, ditDefault);
  712.         
  713.         SetDControlBoolean(dlg, ditAllModules, gModuleID == -1);
  714.         SetDControlBoolean(dlg, ditChosenModules, gModuleID != -1);
  715.         if (gModuleID != -1) {
  716.             NumToString(gModuleID, tmpStr);
  717.             SetDItemText(dlg, ditModuleID, tmpStr);
  718.         }
  719.  
  720.         SetDControlBoolean(dlg, ditAllStreams, gStreamID == -1);
  721.         SetDControlBoolean(dlg, ditChosenStreams, gStreamID != -1);
  722.         if (gStreamID != -1) {
  723.             NumToString(gStreamID, tmpStr);
  724.             SetDItemText(dlg, ditStreamID, tmpStr);
  725.         }
  726.         
  727.         SelectDialogItemText(dlg, ditModuleID, 0, 32767);
  728.         
  729.         // Run the dialog and respond to user events.
  730.         
  731.         ShowWindow(dlg);
  732.         do { 
  733.             ModalDialog(gOKCancelModalFilter, &hitItem);
  734.             switch (hitItem) {
  735.                 case ditAllModules:
  736.                 case ditChosenModules:
  737.                     ToggleDControlBoolean(dlg, ditAllModules);
  738.                     ToggleDControlBoolean(dlg, ditChosenModules);
  739.                     break;
  740.                 case ditAllStreams:
  741.                 case ditChosenStreams:
  742.                     ToggleDControlBoolean(dlg, ditAllStreams);
  743.                     ToggleDControlBoolean(dlg, ditChosenStreams);
  744.                     break;
  745.                 case ditOK:
  746.                 case ditCancel:
  747.                     // do nothing
  748.                     break;
  749.                 default:
  750.                     OTDebugStr("UIPoseFilterDialog: Weird hitItem");
  751.             }
  752.         } while ( hitItem != ditOK && hitItem != ditCancel );
  753.         
  754.         // If OK, gather the results of the dialog and write
  755.         // it back to the global variables.
  756.         
  757.         if (hitItem == ditOK) {
  758.             
  759.             if ( GetDControlBoolean(dlg, ditAllModules) ) {
  760.                 gModuleID = -1;
  761.             } else {
  762.                 GetDItemText(dlg, ditModuleID, tmpStr);
  763.                 StringToNum(tmpStr, &tmpLong);
  764.                 gModuleID = tmpLong;
  765.             }
  766.  
  767.             if ( GetDControlBoolean(dlg, ditAllStreams) ) {
  768.                 gStreamID = -1;
  769.             } else {
  770.                 GetDItemText(dlg, ditStreamID, tmpStr);
  771.                 StringToNum(tmpStr, &tmpLong);
  772.                 gStreamID = tmpLong;
  773.             }
  774.             
  775.             DirtyPreferences();
  776.         }
  777.         
  778.         DisposeDialog(dlg);
  779.         
  780.     } else {
  781.         DisplayError(memFullErr);
  782.     }
  783. }
  784.  
  785. /////////////////////////////////////////////////////////////////////
  786. #pragma mark **** Log Entry Processing *****
  787.  
  788. // gLoggedCount is used to record the total number of entries
  789. // we have logged.  We use this number to maintain a static text
  790. // item in the dialog.
  791.  
  792. static UInt32 gLoggedCount = 0;
  793.  
  794. // gLastLoggedCount and gLastDroppedCount record the last value
  795. // of these two variables we used to fill out the static text fields.
  796. // We keep a record of this so that, each time around the idle loop,
  797. // we don't go to the trouble of updating the static text items unless
  798. // they have changed.
  799.  
  800. static UInt32 gLastLoggedCount = 0;
  801. static UInt32 gLastDroppedCount = 0;
  802.  
  803. static pascal void RecordLogEntry(LogEntryPtr thisEntry, void *refCon)
  804.     // This routine is a callback, called by the logging module (which
  805.     // we in turn called at idle time), when a new log entry has been
  806.     // found.  We call our two logging subsystems (on screen list
  807.     // and file) to log the entry to the appropriate places.  The
  808.     // routines we call are responsible for retaining thisEntry
  809.     // if they need it to hang around; otherwise it will be disposed
  810.     // when this routine returns.
  811. {
  812.     #pragma unused(refCon)
  813.     
  814.     RecordLogEntryToList(thisEntry);
  815.     RecordLogEntryToFile(thisEntry);
  816.     
  817.     gLoggedCount += 1;
  818. }
  819.  
  820. /////////////////////////////////////////////////////////////////////
  821. #pragma mark **** Menu Handling *****
  822.  
  823. static void AdjustMenus(void)
  824.     // This routine adjusts the menus based on the state of the
  825.     // program in preparation for a call to MenuKey or MenuSelect.
  826. {
  827.     MenuHandle menuH;
  828.     Boolean logAtFront;
  829.     Boolean logHasSelection;
  830.     
  831.     // File
  832.     
  833.     menuH = GetMenuHandle(mFile);
  834.     SetMenuItemEnable(menuH, iClose, FrontWindow() == gMainWindow );
  835.     
  836.     // Edit
  837.     
  838.     menuH = GetMenuHandle(mEdit);
  839.     logAtFront = (FrontWindow() == gMainWindow);
  840.     logHasSelection = (LSelectedLine(gLogList) != -1);
  841.     DisableItem(menuH, iUndo);
  842.     SetMenuItemEnable(menuH, iCut, logAtFront && logHasSelection);
  843.     SetMenuItemEnable(menuH, iCopy, logAtFront && logHasSelection);
  844.     DisableItem(menuH, iPaste);
  845.     SetMenuItemEnable(menuH, iClear, logAtFront && logHasSelection);
  846.     SetMenuItemEnable(menuH, iSelectAll, logAtFront && ! LIsEmpty(gLogList) );
  847. }
  848.  
  849. static void CopySelectedListEntriesToClip(void)
  850.     // This routine is used to implement the Copy and Cut
  851.     // menu commands.  It iterates through the list of selected
  852.     // cells and adds the text from each of them into a handle.
  853.     // It then puts that handle into the system scrap.
  854. {
  855.     OSStatus err;
  856.     Cell listCell;
  857.     Handle clipTextH;
  858.     LogEntryPtr anEntry;
  859.     char *entryAsText;
  860.  
  861.     // Create a handle for the text we're copying.
  862.     
  863.     clipTextH = NewHandle(0);
  864.     err = CheckMemError(clipTextH);
  865.     if (err == noErr) {
  866.     
  867.         // Iterate through the selected cells, adding their
  868.         // text representations to the handle.
  869.         
  870.         listCell.v = 0;
  871.         listCell.h = 0;
  872.         while ( LGetSelect(true, &listCell, gLogList) ) {
  873.  
  874.             anEntry = ListGetLogEntryForRow(listCell.v);
  875.             OTAssert("ClearSelectedListEntries: No list data", anEntry != nil);
  876.  
  877.             entryAsText = LogEntryToCString(anEntry);
  878.             err = PtrAndHand(entryAsText, clipTextH, OTStrLength(entryAsText));
  879.             if (err != noErr) {
  880.                 break;
  881.             }
  882.             listCell.v += + 1;
  883.             listCell.h = 0;
  884.         }
  885.     }
  886.     
  887.     // Put the newly created text into the system scrap.
  888.     
  889.     if (err == noErr) {
  890.         (void) ZeroScrap();
  891.         err = PutScrap( GetHandleSize(clipTextH), 'TEXT', *clipTextH );
  892.         if (err > 0) {
  893.             err = 0;
  894.         }
  895.     }
  896.     
  897.     // Clean up.
  898.     
  899.     if ( clipTextH != nil ) {
  900.         DisposeHandle(clipTextH);
  901.     }
  902.     DisplayError(err);
  903. }
  904.  
  905. static void ClearSelectedListEntries(void)
  906.     // This routine is used to implement the Cut and Clear
  907.     // menu commands.  It iterates through the list of selected
  908.     // cells deleting them.
  909. {
  910.     Cell listCell;
  911.     LogEntryPtr anEntry;
  912.     
  913.     // I turn off drawing mode, delete all the selected cells, turn
  914.     // drawing mode back on and then invalidate the entire list.  This
  915.     // causes flashing if there's only one cell selected, but it works
  916.     // much better in the case where there are *lots* of cells selected.
  917.     // I chose to do it this way because I think the latter case will be
  918.     // more prevelant, and I don't have time to handle both cases properly.
  919.  
  920.     LSetDrawingMode(false, gLogList);
  921.     listCell.v = 0;
  922.     listCell.h = 0;
  923.     while ( LGetSelect(true, &listCell, gLogList) ) {
  924.  
  925.         anEntry = ListGetLogEntryForRow(listCell.v);
  926.         OTAssert("ClearSelectedListEntries: No list data", anEntry != nil);
  927.         ReleaseLogEntry(anEntry);
  928.         LDelRow(1, listCell.v, gLogList);
  929.  
  930.         // Don't increment listCell.v because we've just deleted it.
  931.         // LGetSelect will continue looking from the current co-ordinates,
  932.         // which will be the next selected cell.
  933.         // listCell.v += + 1;
  934.         listCell.h = 0;
  935.     }
  936.     LSetDrawingMode(true, gLogList);
  937.     InvalDItem(gMainWindow, ditLogEntryList);
  938. }
  939.  
  940. static void DoMenu(SInt32 menuAndItem)
  941.     // Handle a menu command.  menuAndItme is a longint
  942.     // with the menu number in the top 16 bits and the
  943.     // menu item in the bottom 16, ie the format returned
  944.     // by MenuSelect and MenuKey.
  945.     //
  946.     // Yeah, this should be more than one procedure (-:
  947. {
  948.     SInt16 menu;
  949.     SInt16 item;
  950.     UInt32 startTicks;
  951.     SInt32 junkLong;
  952.     Str255 daName;
  953.     SInt16 junk;
  954.     VersRecHndl versH;
  955.     SInt8 s;
  956.     
  957.     startTicks = TickCount();
  958.     
  959.     menu = menuAndItem >> 16;
  960.     item = menuAndItem & 0x0FFFF;
  961.     switch (menu) {
  962.         case mApple:
  963.             switch (item) {
  964.                 case iAbout:
  965.                     versH = (VersRecHndl) Get1Resource('vers', 1);
  966.                     OTAssert("", versH != nil);
  967.                     s = HGetState( (Handle) versH );
  968.                     HLock( (Handle) versH );
  969.                     ParamText( (**versH).shortVersion, "\p", "\p", "\p");
  970.                     junk = Alert(rAboutBox, gOKModalFilterUPP);
  971.                     HSetState( (Handle) versH, s );
  972.                     break;
  973.                 default:
  974.                     GetMenuItemText(GetMenuHandle(mApple), item, daName);
  975.                     junk = OpenDeskAcc(daName);
  976.                     break;
  977.             }
  978.             break;
  979.         case mFile:
  980.             switch (item) {
  981.                 case iClose:
  982.                     SetMainWindowVisibility(false);
  983.                     DirtyPreferences();
  984.                     break;
  985.                 case iShowHideLogWindow:
  986.                     SetMainWindowVisibility( ! ((WindowPeek) gMainWindow)->visible );
  987.                     DirtyPreferences();
  988.                     break;
  989.                 case iStartStopLogging:
  990.                     if ( LoggingActive() ) {
  991.                         FlashDItem(gMainWindow, ditStop);
  992.                         UIStopLogging();
  993.                     } else {
  994.                         FlashDItem(gMainWindow, ditStart);
  995.                         UIStartLogging();
  996.                     }
  997.                     break;
  998.                 case iScrollWhileLogging:
  999.                     SetScrollWhileLogging( ! gScrollWhileLogging);
  1000.                     DirtyPreferences();
  1001.                     break;
  1002.                 case iQuit:
  1003.                     gQuitNow = true;
  1004.                     break;
  1005.                 default:
  1006.                     OTDebugStr("DoMenu: Weird File menu item");
  1007.                     break;
  1008.             }
  1009.             break;
  1010.         case mEdit:
  1011.             OTAssert("DoMenu: Edit menu should be disabled if gMainWindow not at front", gMainWindow == FrontWindow());
  1012.             switch (item) {
  1013.                 case iCut:
  1014.                     CopySelectedListEntriesToClip();
  1015.                     ClearSelectedListEntries();
  1016.                     break;
  1017.                 case iCopy:
  1018.                     CopySelectedListEntriesToClip();
  1019.                     break;
  1020.                 case iClear:
  1021.                     ClearSelectedListEntries();
  1022.                     break;
  1023.                 case iSelectAll:
  1024.                     LSelectAll(gLogList);
  1025.                     break;
  1026.                 default:
  1027.                     OTDebugStr("DoMenu: Weird Edit menu item");
  1028.                     break;
  1029.             }
  1030.         default:
  1031.             // OTDebugStr("DoMenu: Weird menu item");
  1032.             break;
  1033.     }
  1034.     
  1035.     // Unhighlight the menu, delaying to avoid a quick flash.
  1036.     
  1037.     if ( ! gQuitNow ) {
  1038.         while ( TickCount() < startTicks + 6 ) {
  1039.             Delay(1, &junkLong);
  1040.         }
  1041.         HiliteMenu(0);
  1042.     }
  1043. }
  1044.  
  1045. /////////////////////////////////////////////////////////////////////
  1046. #pragma mark **** Low Level Event Handling *****
  1047.  
  1048. static void DoMouseDown(EventRecord *event)
  1049.     // Handle a mouse down event.  This is standard
  1050.     // Mac OS stuff.  Call FindWindow to determine what
  1051.     // type and event it was (and what window it happened in)
  1052.     // and then handle each case.
  1053. {
  1054.     SInt16 partCode;
  1055.     WindowPtr hitWindow;
  1056.     long growResult;
  1057.     
  1058.     partCode = FindWindow(event->where, &hitWindow);
  1059.     switch ( partCode ) {
  1060.         case inGoAway:
  1061.             if ( TrackGoAway(hitWindow, event->where) ) {
  1062.                 if ( hitWindow == gMainWindow ) {
  1063.                     SetMainWindowVisibility(false);
  1064.                     DirtyPreferences();
  1065.                 } else {
  1066.                     OTDebugStr("DoMouseDown: Does not handle closing other windows");
  1067.                 }
  1068.             }
  1069.             break;
  1070.         case inMenuBar:
  1071.             AdjustMenus();
  1072.             DoMenu(MenuSelect(event->where));
  1073.             break;
  1074.         case inDrag:
  1075.             DragWindow(hitWindow, event->where, &qd.screenBits.bounds);
  1076.             DirtyPreferences();
  1077.             break;
  1078.         case inGrow:
  1079.             growResult = GrowWindow(hitWindow, event->where, &gMainWindowGrowBounds);
  1080.             if ( growResult != 0 ) {
  1081.                 Rect tmpRect;
  1082.                 
  1083.                 if ( hitWindow == gMainWindow ) {
  1084.                     SizeMainWindow(growResult, growResult >> 16);
  1085.                     DirtyPreferences();
  1086.                 } else {
  1087.                     SizeWindow(hitWindow, growResult, growResult >> 16, true);
  1088.                 }
  1089.                 
  1090.                 tmpRect = hitWindow->portRect;
  1091.                 tmpRect.top = tmpRect.bottom - 15;
  1092.                 InvalRect(&tmpRect);
  1093.                 tmpRect = hitWindow->portRect;
  1094.                 tmpRect.left = tmpRect.right - 15;
  1095.                 InvalRect(&tmpRect);
  1096.             }
  1097.             break;
  1098.         case inZoomIn:
  1099.         case inZoomOut:
  1100.             if ( TrackBox(hitWindow, event->where, partCode) ) {
  1101.                 ZoomWindow(hitWindow, partCode, false);
  1102.                 if (hitWindow == gMainWindow) {
  1103.                     SizeMainWindow(hitWindow->portRect.right - hitWindow->portRect.left,
  1104.                                     hitWindow->portRect.bottom - hitWindow->portRect.top);
  1105.                 }
  1106.                 
  1107.                 // Force everything to redraw.  I know this isn't perfect,
  1108.                 // but I'd rather have window zooming working but flashy
  1109.                 // than not have it.
  1110.                 
  1111.                 EraseRect(&hitWindow->portRect);
  1112.                 InvalRect(&hitWindow->portRect);
  1113.             }
  1114.             break;
  1115.         default:
  1116.             // do nothing
  1117.             break;
  1118.     }
  1119. }
  1120.  
  1121. static void DoIdle(void)
  1122.     // Called each time through the main event loop.  This routine
  1123.     // handles getting the new log entries out of the log module
  1124.     // and into RecordLogEntry, which sends them to the file and list
  1125.     // logging code.  It also idles the file and preferences modules,
  1126.     // which delay actions for a time to prevent unnecessary disk thrashing.
  1127. {
  1128.     Str255 tmpStr;
  1129.     UInt32 droppedCount;
  1130.     
  1131.     // Don't ask me why, or I'll start to whimper.
  1132.  
  1133.     OTIdle();
  1134.  
  1135.     // OK, I'll explain.  Basically there's a weird concurrency
  1136.     // hole in OT such that, if you don't have any other network
  1137.     // activity, strlog messages will never get delivered
  1138.     // to the client.  I've reported this as a bug [Radar ID â€¢â€¢â€¢],
  1139.     /// but it's easy to work around (calling OTIdle will deliver
  1140.     // the messages) and it's so deep in the OT kernel that 
  1141.     // I'm not sure anyone will ever want to take the risk
  1142.     // associated with fixing it.
  1143.     
  1144.     ForEachNewLogEntry(RecordLogEntry, nil);
  1145.     
  1146.     FileLoggingIdle();
  1147.  
  1148.     PreferencesIdle();
  1149.  
  1150.     // Update the static text to reflect the number
  1151.     // of logs we've seen and/or dropped.
  1152.     
  1153.     if ( gLoggedCount != gLastLoggedCount ) {
  1154.         NumToString(gLoggedCount, tmpStr);
  1155.         SetDItemText(gMainWindow, ditLoggedCount, tmpStr);
  1156.         gLastLoggedCount = gLoggedCount;
  1157.     }
  1158.     
  1159.     droppedCount = NumberOfDroppedLogEntries();
  1160.     if ( droppedCount != gLastDroppedCount ) {
  1161.         NumToString(droppedCount, tmpStr);
  1162.         SetDItemText(gMainWindow, ditDroppedCount, tmpStr);
  1163.         gLastDroppedCount = droppedCount;
  1164.     }
  1165. }
  1166.  
  1167. static pascal void GetLogListCellText(ListHandle listH, Cell listCell, Str255 cellText)
  1168.     // This routine is a callback from LDoKey to get the text of
  1169.     // a cell.  We return the sequence number for the log entry.
  1170.     // This allows LDoKey to implement "select by typing".
  1171. {
  1172.     #pragma unused(listH)
  1173.     LogEntryPtr thisEntry;
  1174.     
  1175.     cellText[0] = 0;
  1176.     
  1177.     thisEntry = ListGetLogEntryForRow(listCell.v);
  1178.     OTAssert("GetLogListCellText: No data for cell", thisEntry != nil);
  1179.     if (thisEntry != nil) {
  1180.         NumToString(thisEntry->fLogHeader.seq_no, cellText);
  1181.     }
  1182. }
  1183.  
  1184. static void DoUpdateEvent(EventRecord *event)
  1185.     // In general we handle update events through
  1186.     // the Dialog Manager DialogSelect routine.  However,
  1187.     // for the main window, we have to manually draw the
  1188.     // grow box.  This is not as easy as it might seem, because
  1189.     // we don't want the silly fake scroll bars in our window.
  1190.     // We we set the clip region to the bottom right 15 x 15
  1191.     // rectangle, so we get the grow box and nothing else.
  1192.     //
  1193.     // Of course, Mac OS 8 Appearance does silly things here
  1194.     // too, because the grow box is now part of the window
  1195.     // structure region.  But it recognises the call to DrawGrowIcon
  1196.     // and does The Right Thing (tm).
  1197. {
  1198.     RgnHandle tmpRgn;
  1199.     Rect growRect;
  1200.  
  1201.     if ( (WindowPtr) event->message == gMainWindow ) {
  1202.         tmpRgn = NewRgn();
  1203.         OTAssert("MainEventLoop: Could not create temporary region", tmpRgn != nil);
  1204.         
  1205.         CopyRgn(gMainWindow->clipRgn, tmpRgn);
  1206.         growRect = gMainWindow->portRect;
  1207.         growRect.top = growRect.bottom - 15;
  1208.         growRect.left = growRect.right - 15;
  1209.         ClipRect(&growRect);
  1210.         
  1211.         DrawGrowIcon( (WindowPtr) event->message );
  1212.         
  1213.         SetClip(tmpRgn);
  1214.         DisposeRgn(tmpRgn);
  1215.     }
  1216. }
  1217.  
  1218. static void DoMainWindowHit(EventRecord *event, SInt16 hitItem, SInt16 oldTraceLevel)
  1219.     // Handle a hit on an item in the main dialog window.
  1220.     // A big switch state with a bunch of UI twiddling
  1221.     // that, if we had a real windowing toolbox, would not
  1222.     // be necessary!
  1223.     //
  1224.     // oldTraceLevel is the old value of the trace level popup
  1225.     // menu.  The MainEventLoop saves this value before calling
  1226.     // DialogSelect.  DialogSelect will change the popup and
  1227.     // tells us that it's done so.  We then ask the user to
  1228.     // confirm whether they want to stop logging.  If they say
  1229.     // they don't, we reset the popup back to the old value.
  1230. {
  1231.     Point localWhere;
  1232.     
  1233.     switch ( hitItem ) {
  1234.         case ditStart:
  1235.             UIStartLogging();
  1236.             break;
  1237.         case ditStop:
  1238.             UIStopLogging();
  1239.             break;
  1240.  
  1241.         case ditLogErrors:
  1242.         case ditLogTraces:
  1243.         case ditLogToFile:
  1244.             if ( UIConfirmAndStop() ) {
  1245.                 ToggleDControlBoolean(gMainWindow, hitItem);
  1246.                 DirtyPreferences();
  1247.             }
  1248.             break;
  1249.  
  1250.         case ditAllTraces:
  1251.         case ditTracesMatching:
  1252.             if ( UIConfirmAndStop() ) {
  1253.                 SetDControlBoolean(gMainWindow, ditAllTraces, hitItem == ditAllTraces);
  1254.                 SetDControlBoolean(gMainWindow, ditTracesMatching, hitItem == ditTracesMatching);
  1255.                 SetDControlEnable(gMainWindow, ditSetupFilter, hitItem == ditTracesMatching);
  1256.                 DirtyPreferences();
  1257.             }
  1258.             break;
  1259.  
  1260.         case ditSetupFilter:
  1261.             if ( UIConfirmAndStop() ) {
  1262.                 UIPoseFilterDialog();
  1263.             }
  1264.             break;
  1265.             
  1266.         case ditTraceLevel:
  1267.             if ( UIConfirmAndStop() ) {
  1268.                 DirtyPreferences();
  1269.             } else {
  1270.                 SetDControlValue(gMainWindow, ditTraceLevel, oldTraceLevel);
  1271.             }
  1272.             break;
  1273.             
  1274.         case ditLogEntryList:
  1275.             localWhere = event->where;
  1276.             GlobalToLocal(&localWhere);
  1277.             (void) LClick(localWhere, event->modifiers, gLogList);
  1278.             break;
  1279.         default:
  1280.             OTDebugStr("DoMainWindowHit: Unexpected hitItem");
  1281.             break;
  1282.     }
  1283. }
  1284.  
  1285. static void MainEventLoop(void)
  1286. {
  1287.     EventRecord event;
  1288.     WindowRef hitWindow;
  1289.     SInt16 hitItem;
  1290.     SInt16 oldTraceLevel;
  1291.     
  1292.     gQuitNow = false;
  1293.     do {
  1294.         DoIdle();
  1295.         
  1296.         (void) WaitNextEvent(everyEvent, &event, 60, nil);
  1297.  
  1298.         SetPort(gMainWindow);
  1299.         
  1300.         if ( IsDialogEvent(&event) ) {
  1301.             oldTraceLevel = GetDControlValue(gMainWindow, ditTraceLevel);
  1302.             if ( DialogSelect(&event, &hitWindow, &hitItem) ) {
  1303.                 if (hitWindow == gMainWindow) {
  1304.                     DoMainWindowHit(&event, hitItem, oldTraceLevel);
  1305.                 } else {
  1306.                     OTDebugStr("MainEventLoop: What other dialogs?");
  1307.                 }
  1308.             }
  1309.         }
  1310.         switch (event.what) {
  1311.             case keyDown:
  1312.             case autoKey:
  1313.                 if ( event.what == keyDown && (event.modifiers & cmdKey) != 0 ) {
  1314.                     AdjustMenus();
  1315.                     DoMenu(MenuKey(event.message & charCodeMask));
  1316.                 } else if ( FrontWindow() == gMainWindow) {
  1317.                     UInt8 typedChar;
  1318.                     
  1319.                     typedChar = (event.message & charCodeMask);
  1320.                     if ( typedChar == kClearChar ||
  1321.                             typedChar == kBackSpaceChar ||
  1322.                             typedChar == kDelChar ) {
  1323.                         ClearSelectedListEntries();
  1324.                     } else {
  1325.                         LDoKey(gLogList, &event, GetLogListCellText);
  1326.                     }
  1327.                 }
  1328.                 break;
  1329.             case mouseDown:
  1330.                 DoMouseDown(&event);
  1331.                 break;
  1332.             case updateEvt:
  1333.                 DoUpdateEvent(&event);
  1334.                 break;
  1335.             case activateEvt:
  1336.                 if ( (WindowPtr) event.message == gMainWindow ) {
  1337.                     LActivate( (event.modifiers & 1) != 0, gLogList);
  1338.                 }
  1339.                 break;
  1340.             case kHighLevelEvent:
  1341.                 (void) AEProcessAppleEvent(&event);
  1342.                 break;
  1343.             default:
  1344.                 // do nothing
  1345.                 break;
  1346.         }
  1347.     } while ( ! gQuitNow );
  1348.  
  1349.     // As we're quitting, we'd better shut down the logging
  1350.     // systems.
  1351.         
  1352.     if ( LoggingActive() ) {
  1353.         FlashDItem(gMainWindow, ditStop);
  1354.         UIStopLogging();
  1355.     }
  1356. }
  1357.  
  1358. /////////////////////////////////////////////////////////////////////
  1359. #pragma mark **** Prefs Management *****
  1360.  
  1361. enum {
  1362.     kPrefsMagic = kCreator
  1363. };
  1364.  
  1365. // PrefsRecord is stored in Internet Config (if it is
  1366. // installed) because it's a handy preferences system
  1367. // and I really don't want to create yet another file
  1368. // in the Preferences folder.  Because it's stored
  1369. // on the disk, it has to be 68K aligned (becasue it might
  1370. // be written by a 68K and read by a PPC, or vice versa).
  1371. //
  1372. // The fields in the record pretty much mirror the state
  1373. // in the user interface elements.  We save all this state
  1374. // so the user (namely me during testing!) doesn't have to
  1375. // reset it each time they launch the program.
  1376.  
  1377. #pragma options align=mac68k
  1378. struct PrefsRecord {
  1379.     OSType    magic;                // kPrefsMagic
  1380.     Rect    mainWindowPosition;
  1381.     SInt16    moduleID;
  1382.     SInt16    streamID;
  1383.     Boolean    logToFile;
  1384.     Boolean    logErrors;
  1385.     Boolean    logTraces;
  1386.     Boolean    tracesMatching;
  1387.     Boolean mainWindowVisible;
  1388.     Boolean scrollWhileLogging;
  1389. };
  1390. typedef struct PrefsRecord PrefsRecord, *PrefsRecordPtr;
  1391. #pragma options align=reset
  1392.  
  1393. static UInt8 *gICPrefsKey = "\p536C5677•PrefsRecord";
  1394.     // The Internet Config key we use to save the PrefsRecord.
  1395.  
  1396. static ICInstance gICInstance = nil;
  1397.     // Our connection to Internet Config.  Note that
  1398.     // gICInstance != nil implies that it's safe to call IC.
  1399.  
  1400. enum {
  1401.     kPrefChangeWriteDelay = 600,
  1402.         // The length of time between when the preferences
  1403.         // are dirtied and when we write them.  This allows
  1404.         // multiple preference writes to be collated into one,
  1405.         // thereby saving some disk thrashing.
  1406.     
  1407.     kPrefsCleanTicks = (UInt32) -(kPrefChangeWriteDelay+1)
  1408.         // When the preferences are clean we set gTicksOfLastPrefChange
  1409.         // to this value to prevent further writes from happening.
  1410.         // This corresponds to "far in the future".
  1411. };
  1412.  
  1413. static UInt32 gTicksOfLastPrefChange = kPrefsCleanTicks;
  1414.     // The TickCount time at which we last modified the
  1415.     // preferences.  If TickCount is more than this value plus
  1416.     // kPrefChangeWriteDelay, we need to save the preferences now.
  1417.  
  1418. static void RestorePreferences(void)
  1419.     // Restore the preferences from Internet Config,
  1420.     // and put all the saved values back into the appropriate
  1421.     // state holder in the program.
  1422. {
  1423.     OSStatus err;
  1424.     PrefsRecord prefs;
  1425.     SInt32 prefsSize;
  1426.     ICAttr junkAttr;
  1427.     
  1428.     OTAssert("RestorePreferences: Need a main window to do this", gMainWindow != nil);
  1429.     OTAssert("RestorePreferences: Need a log list to do this", gLogList != nil);
  1430.  
  1431.     // Start by creating a connection to IC, only if it's present
  1432.     // (and we linked to it).
  1433.         
  1434.     err = noErr;
  1435.     #if GENERATINGCFM
  1436.         if ( (void *) ICStart == (void *) kUnresolvedCFragSymbolAddress ) {
  1437.             err = -1;
  1438.         }
  1439.     #endif
  1440.     if (err == noErr) {
  1441.         err = ICStart(&gICInstance, kCreator);
  1442.     }
  1443.     if (err == noErr) {
  1444.         err = ICFindConfigFile(gICInstance, 0, nil);
  1445.     }
  1446.     
  1447.     // Then read the preferencs from IC.  Ignore truncated errors.
  1448.     // It probably means that the preferences were last written by 
  1449.     // a more modern version of this program, but it should damn well
  1450.     // make sure that our fields are the first fields of its preferences
  1451.     // record.
  1452.     
  1453.     if (err == noErr) {
  1454.         prefsSize = sizeof(PrefsRecord);
  1455.         err = ICGetPref(gICInstance, gICPrefsKey, &junkAttr, (void *) &prefs, &prefsSize);
  1456.         // err = -666;
  1457.         if (err == icTruncatedErr) {
  1458.             prefsSize = sizeof(PrefsRecord);
  1459.             err = noErr;
  1460.         }
  1461.     }
  1462.     
  1463.     // Basical sanity checks.
  1464.     
  1465.     if (err == noErr && prefsSize < sizeof(PrefsRecord)) {
  1466.         err = -2;
  1467.     }
  1468.     if (err == noErr && prefs.magic != kPrefsMagic) {
  1469.         err = -3;
  1470.     }
  1471.  
  1472.     // If we could not read preferences, use defaults instead.
  1473.         
  1474.     if (err != noErr) {
  1475.         prefs.magic = kPrefsMagic;
  1476.         GetWindowRect(gMainWindow, &prefs.mainWindowPosition);
  1477.         prefs.moduleID = 0;
  1478.         prefs.streamID = 0;
  1479.         prefs.logToFile = false;
  1480.         prefs.logErrors = true;
  1481.         prefs.logTraces = true;
  1482.         prefs.tracesMatching = false;
  1483.         prefs.mainWindowVisible = true;
  1484.         prefs.scrollWhileLogging = true;
  1485.     }
  1486.     
  1487.     // Now set up the program state from the preferences.
  1488.     
  1489.     gModuleID = prefs.moduleID;
  1490.     gStreamID = prefs.streamID;
  1491.     
  1492.     SetDControlBoolean(gMainWindow, ditLogToFile, prefs.logToFile);
  1493.     SetDControlBoolean(gMainWindow, ditLogErrors, prefs.logErrors);
  1494.     SetDControlBoolean(gMainWindow, ditLogTraces, prefs.logTraces);
  1495.     SetDControlBoolean(gMainWindow, ditTracesMatching, prefs.tracesMatching);
  1496.     SetDControlBoolean(gMainWindow, ditAllTraces, ! prefs.tracesMatching);
  1497.  
  1498.     SetDControlEnable(gMainWindow, ditSetupFilter, prefs.tracesMatching);
  1499.     
  1500.     MoveWindow(gMainWindow, prefs.mainWindowPosition.left, prefs.mainWindowPosition.top, false);
  1501.     SizeMainWindow(prefs.mainWindowPosition.right - prefs.mainWindowPosition.left,
  1502.                     prefs.mainWindowPosition.bottom - prefs.mainWindowPosition.top);
  1503.     SetMainWindowVisibility(prefs.mainWindowVisible);
  1504.     SetScrollWhileLogging(prefs.scrollWhileLogging);
  1505. }
  1506.  
  1507. static void SavePreferences(void)
  1508.     // Save the preferences to Internet Config.
  1509. {
  1510.     OSStatus junk;
  1511.     PrefsRecord prefs;
  1512.  
  1513.     // Don't even think about this if we don't have a connection
  1514.     // to IC.
  1515.         
  1516.     if ( gICInstance != nil ) {
  1517.     
  1518.         // Fill out the PrefsRecord from the program state
  1519.         // and write it out.
  1520.         
  1521.         prefs.magic = kPrefsMagic;
  1522.         GetWindowRect(gMainWindow, &prefs.mainWindowPosition);
  1523.         prefs.moduleID = gModuleID;
  1524.         prefs.streamID = gStreamID;
  1525.         prefs.logToFile = GetDControlBoolean(gMainWindow, ditLogToFile);
  1526.         prefs.logErrors = GetDControlBoolean(gMainWindow, ditLogErrors);
  1527.         prefs.logTraces = GetDControlBoolean(gMainWindow, ditLogTraces);
  1528.         prefs.tracesMatching = GetDControlBoolean(gMainWindow, ditTracesMatching);
  1529.         prefs.mainWindowVisible =  ((WindowPeek) gMainWindow)->visible;
  1530.         prefs.scrollWhileLogging = gScrollWhileLogging;
  1531.         
  1532.         junk = ICSetPref(gICInstance, gICPrefsKey, ICattr_no_change, (void *) &prefs, sizeof(PrefsRecord));
  1533.         OTAssert("SavePreferences: Error writing prefs", junk == noErr);
  1534.     }
  1535.     gTicksOfLastPrefChange = kPrefsCleanTicks;
  1536. }
  1537.  
  1538. static void DirtyPreferences(void)
  1539.     // Called by the general code when something that is
  1540.     // stored in the prefs is modified.  We note the time
  1541.     // this happens, and the idle function should eventually
  1542.     // notice this and call SavePreferences.
  1543. {
  1544.     gTicksOfLastPrefChange = TickCount();
  1545. }
  1546.  
  1547. static void PreferencesIdle(void)
  1548.     // Our idle function, called by DoIdle, once each time around
  1549.     // the event loop.  We see how long the preferences have been dirty
  1550.     // and write them if it's been too long.
  1551. {
  1552.     if ( TickCount() > gTicksOfLastPrefChange + kPrefChangeWriteDelay) {
  1553.         SavePreferences();
  1554.     }
  1555. }
  1556.  
  1557. static void TermPreferences(void)
  1558.     // This routine is called when the application quits
  1559.     // to save any preferences that haven't yet been saved by
  1560.     // our idle function and shut down our connection to IC.
  1561. {
  1562.     OSStatus junk;
  1563.     
  1564.     if ( gICInstance != nil ) {
  1565.     
  1566.         if ( gTicksOfLastPrefChange != kPrefsCleanTicks ) {
  1567.             SavePreferences();
  1568.         }
  1569.     
  1570.         junk = ICStop(gICInstance);
  1571.         OTAssert("TermPreferences: Error closing IC", junk == noErr);
  1572.         gICInstance = nil;
  1573.     }
  1574. }
  1575.  
  1576. /////////////////////////////////////////////////////////////////////
  1577. #pragma mark **** Startup and Shutdown Code *****
  1578.  
  1579. static void InitMacToolbox(void)
  1580.     // Init all standard Mac OS managers.
  1581.     // How many times have I written this?
  1582. {
  1583.     InitGraf(&qd.thePort);
  1584.     InitFonts();
  1585.     InitWindows();
  1586.     InitMenus();
  1587.     TEInit();
  1588.     InitDialogs(nil);
  1589.     MaxApplZone();
  1590.     InitCursor();
  1591. }
  1592.  
  1593. // InitOpenTransportWithMemoryLimit Big Picture
  1594. // --------------------------------------------
  1595. //
  1596. // The LogEngine uses the OT memory allocation routines
  1597. // (OTAllocMem, OTFreeMem) to allocate space for log entries in
  1598. // the notifier.  This memory comes from an ASLM memory pool
  1599. // that OT creates for us when we call InitOpenTransport.  However,
  1600. // this pool has some bad characteristics:
  1601. //
  1602. //     1.    The pool starts off very small, and only grows when we allocate
  1603. //         memory from it.  As we do all our allocation from our notifier
  1604. //        (which is interrupt time with respect to the system Memory Manager)
  1605. //        the pool can't grow immediately.  So the pool will often run
  1606. //        be full (ie OTAllocMem will return nil) even though the application
  1607. //        has plenty of memory.  The pool will later grow, but we've already
  1608. //        dropped the log entry on the floor.
  1609. //
  1610. //    2.    Because the pool starts off small and grows by pieces, we get
  1611. //        an extremely fragmented pool.  While this works, its definitely
  1612. //        sub-optimal.
  1613. //
  1614. //    3.    If we're being hammered by strlog (ie people are calling strlog a
  1615. //        lot), the pool will keep growing and there's nothing to stop
  1616. //        the pool consuming our entire application heap.  When it does so,
  1617. //        various toolbox routines (eg QuickDraw) fail ungracefully, ie
  1618. //        SysError(25).
  1619. //
  1620. // There are a number of steps in my solution to this.  The first step
  1621. // is to call InitLibraryManager myself.  This allows me to specify
  1622. // the size of the pool. InitOpenTransport notices that I have inited ASLM
  1623. // myself and doesn't do it itself.  Thus OTAllocMem gets its memory from
  1624. // whatever pool ASLM created.  This gets around problems 1 and 2.
  1625. //
  1626. // The second step is to create a subsidiary zone within my application heap
  1627. // and specify that ASLM should create its pool in that zone (by supplying
  1628. // kCurrentZone to InitLibraryManager).  Thus the pool can grow up to the
  1629. // point where the memory in the subsidiary zone is exhausted.  At that point,
  1630. // the pool will no longer grow.  So the pool will not steal memory from
  1631. // the main application heap.  This gets around problem 3.
  1632. //
  1633. // There are a number of other ways I could have achieved the same results.
  1634. // The ASLM memory manager is very flexible.  For example, I could have removed
  1635. // OT's TPoolNotifier from the pool, which would prevent the pool from growing.
  1636. // However, this solution does not require me to use any ASLM C++ stuff,
  1637. // which makes the code more compiler independent.
  1638.  
  1639. enum {
  1640.     kBytesReservedForToolboxInApplicationZone = 100L * 1024L,
  1641.         // This value represents the minimum number of contiguous
  1642.         // bytes that should remain in the application heap after
  1643.         // we've created the subsidiary zone.
  1644.         
  1645.     kBytesReservedForASLMInSubsidaryzone = 2048,
  1646.         // This value represents the number of bytes in the subsidiary
  1647.         // zone we should leave lying around for general purpose ASLM
  1648.         // use.  The remaining bytes in the subsidiary zone are
  1649.         // dedicated to the ASLM memory pool, ie are passed as the pool
  1650.         // size to InitLibraryManager.
  1651.         
  1652.     kMinimumBytesForUsInSubsidiaryZone = 10 * 1024
  1653.         // This value represents the minimum pool size we pass to
  1654.         // InitLibraryManager.  If we can't create a pool of at least
  1655.         // this size, the application doesn't start up.
  1656. };
  1657.  
  1658. static OSStatus InitOpenTransportWithMemoryLimit(void)
  1659.     // See above for an explanation of the big picture here.
  1660. {
  1661.     OSStatus err;
  1662.     SInt32 junkTotalFree;
  1663.     SInt32 contigFree;
  1664.     SInt32 zoneSize;
  1665.     Ptr gSubsidiaryZone;
  1666.     THz oldZone;
  1667.  
  1668.     // Debugger();
  1669.     
  1670.     // First call the system Memory Manager to determine the largest
  1671.     // contiguous block in the heap.
  1672.     
  1673.     PurgeSpace(&junkTotalFree, &contigFree);
  1674.     
  1675.     // If it's too small for our toolbox needs, bail out.
  1676.     
  1677.     err = noErr;
  1678.     if (contigFree < kBytesReservedForToolboxInApplicationZone) {
  1679.         err = memFullErr;
  1680.     }
  1681.     
  1682.     // Now calculate the size of the zone we're going to create.
  1683.     // It's the size of the largest contiguous block, minus
  1684.     // the size of we reserve for toolbox needs, rounded to the nearest KB.
  1685.     // If the zone size isn't big enough enough to hold our minimum
  1686.     // pool size and the amount we reserve for ASLM, bail out.
  1687.     
  1688.     if (err == noErr) {
  1689.         zoneSize = contigFree - kBytesReservedForToolboxInApplicationZone;
  1690.         zoneSize = zoneSize & ~0x003FF;
  1691.         if (zoneSize < (kBytesReservedForASLMInSubsidaryzone + kMinimumBytesForUsInSubsidiaryZone)) {
  1692.             err = memFullErr;
  1693.         }
  1694.     }
  1695.     
  1696.     // Allocate the memory for our zone and create a zone in that
  1697.     // block.  Then init ASLM, telling it to create a pool that
  1698.     // takes up the entire zone (minus the ASLM overhead factor)
  1699.     // in the current zone, ie the zone we just created.  Finally,
  1700.     // initialise OT.  OT will see that we've inited ASLM and use
  1701.     // the pool that ASLM created (in the zone we created) for
  1702.     // satisfying OTAllocMem requests.
  1703.     
  1704.     if (err == noErr) {
  1705.         gSubsidiaryZone = NewPtr(zoneSize);
  1706.         OTAssert("InitApplication: Couldn't get the memory but preflight says its there", gSubsidiaryZone != nil);
  1707.         OTAssert("InitApplication: Just being paranoid", MemError() == noErr);
  1708.  
  1709.         oldZone = GetZone();
  1710.  
  1711.         InitZone(nil, 16, gSubsidiaryZone + zoneSize, gSubsidiaryZone);
  1712.         OTAssert("InitApplication: InitZone failed", MemError() == noErr);
  1713.  
  1714.         // InitZone sets the current zone to the newly created zone,
  1715.         // so I don't have to do it myself.
  1716.                 
  1717.         err = InitLibraryManager(zoneSize - kBytesReservedForASLMInSubsidaryzone, kCurrentZone, kNormalMemory);
  1718.         if (err == noErr) {
  1719.             err = InitOpenTransport();
  1720.             if (err != noErr) {
  1721.                 CleanupLibraryManager();
  1722.             }
  1723.         }
  1724.         
  1725.         SetZone(oldZone);
  1726.     }
  1727.  
  1728.     // This code is used to artificially consume all memory
  1729.     // available through OTAllocMem.  I used this to test
  1730.     // that the above strategy works.  It's commented out now
  1731.     // because I'm happy that code works, but I want the code
  1732.     // around just in case I need to look at this situation again.
  1733.     
  1734.     #define TestByExhaustingOTMemoryPool 0
  1735.     #if TestByExhaustingOTMemoryPool
  1736.         if (err == noErr) {
  1737.             void *junk;
  1738.             
  1739.             do {
  1740.                 junk = OTAllocMem(55);
  1741.             } while (junk != nil);
  1742.             Debugger();
  1743.         }
  1744.     #endif
  1745.     
  1746.     return err;
  1747. }
  1748.     
  1749. static OSStatus InitApplication(void)
  1750.     // Initialises the application-specific stuff.
  1751. {
  1752.     OSStatus err;
  1753.     OSStatus junk;
  1754.     Handle mbarH;
  1755.     MenuHandle menuH;
  1756.     Rect viewRect;
  1757.     Rect dataBounds;
  1758.     Cell cellSize;
  1759.     
  1760.     // Initialise the Internet Config libraries that we use.  Note
  1761.     // that these are different from our connection to the Internet
  1762.     // Config extension, which is setup in RestorePreferences.  This
  1763.     // stuff is source code from IC (which is public domain, yay!) that
  1764.     // we've compiled and linked into our application for doing things
  1765.     // like managing List Manager lists, driving dialogs, etc.
  1766.     
  1767.     InitICDialogs();
  1768.     InitListManagerMiscSubs();
  1769.     
  1770.     // Setup the menu bar.
  1771.     
  1772.     mbarH = GetNewMBar(rMenuBar);
  1773.     OTAssert("InitApplication: Could not get menu bar", mbarH != nil);
  1774.     SetMenuBar(mbarH);
  1775.     InvalMenuBar();
  1776.     
  1777.     // Setup the Apple menu.
  1778.     
  1779.     menuH = GetMenuHandle(mApple);
  1780.     OTAssert("InitApplication: Could not get Apple menu", menuH != nil);
  1781.     AppendResMenu(menuH, 'DRVR');
  1782.     
  1783.     // Install our AppleEvent handlers.
  1784.     
  1785.     junk = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication, NewAEEventHandlerProc(AEOpenApplicationHandler), 0, false);
  1786.     OTAssert("InitApplication: Could not install event handler", junk == noErr);
  1787.     junk = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments, NewAEEventHandlerProc(AEOpenDocumentsHandler), 0, false);
  1788.     OTAssert("InitApplication: Could not install event handler", junk == noErr);
  1789.     junk = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication, NewAEEventHandlerProc(AEQuitApplicationHandler), 0, false);
  1790.     OTAssert("InitApplication: Could not install event handler", junk == noErr);
  1791.  
  1792.     // Create the main window.
  1793.         
  1794.     gMainWindow = GetNewDialog(rMainDialog, nil, (WindowPtr) -1L);
  1795.     OTAssert("InitApplication: Could not create main window", gMainWindow != nil);
  1796.  
  1797.     gMainWindowGrowBounds.top = gMainWindow->portRect.bottom;
  1798.     gMainWindowGrowBounds.left = gMainWindow->portRect.right;
  1799.     gMainWindowGrowBounds.bottom = qd.screenBits.bounds.bottom;
  1800.     gMainWindowGrowBounds.right = qd.screenBits.bounds.right;
  1801.     
  1802.     // And the list that's in the main window.
  1803.     
  1804.     gListDefProcUPP = NewListDefProc(ListDefProc);
  1805.     OTAssert("InitApplication: Could not create UPP for ListDefProc", gListDefProcUPP != nil);
  1806.     
  1807.     GetDItemRect(gMainWindow, ditLogEntryList, &viewRect);
  1808.     viewRect.bottom -= 15;
  1809.     viewRect.right -= 15;
  1810.     SetRect(&dataBounds, 0, 0, 1, 0);
  1811.     // SetPt(&cellSize, viewRect.right - viewRect.left, 16);
  1812.     SetPt(&cellSize, 1000, 16);
  1813.     gLogList = LNew(&viewRect,            // rView
  1814.                     &dataBounds,        // dataBounds
  1815.                     cellSize,            // cSize
  1816.                     rSimpleLDEF,        // theProc
  1817.                     gMainWindow,        // theWindow
  1818.                     true,                // drawIt
  1819.                     true,                // hasGrow
  1820.                     true,                // scrollHoriz
  1821.                     true);                // scrollVert
  1822.     OTAssert("InitApplication: Could not create ListHandle", gLogList != nil);
  1823.     (**gLogList).refCon = (long) gListDefProcUPP;
  1824.     
  1825.     gListUserItemUpdateUPP = NewUserItemProc(ListUserItemUpdateProc);
  1826.     OTAssert("InitApplication: Could not create UPP for ListUserItemUpdateProc", gListUserItemUpdateUPP != nil);
  1827.  
  1828.     SetDItemHandle(gMainWindow, ditLogEntryList, (Handle) gListUserItemUpdateUPP);
  1829.     
  1830.     // Restore our saved preferences, or setup defaults if
  1831.     // we have none.
  1832.     
  1833.     RestorePreferences();
  1834.     
  1835.     // Sync the Start and Stop buttons with the state of
  1836.     // the back end.
  1837.     
  1838.     UpdateStartStopUI();
  1839.     
  1840.     // Startup Open Transport.  We have to do some special
  1841.     // things to prevent OT eating our entire heap.  See
  1842.     // the comments associated with this routine.
  1843.     
  1844.     err = InitOpenTransportWithMemoryLimit();
  1845.  
  1846.     return err;
  1847. }
  1848.  
  1849. static void TermApplication(void)
  1850.     // Shut down our application.  This is normal quitting,
  1851.     // ie via AppleEvent or via menu command.  The main event
  1852.     // loop has already shut down the logging stuff.  All we
  1853.     // need to do is close down the connection to Open Transport,
  1854.     // ASLM and Internet Config.
  1855.     //
  1856.     // Note that we make no attempt to clean up our toolbox stuff
  1857.     // or memory allocates.  The Process Manager will do that for us
  1858.     // and there's no point writing extra code (and extra bugs) to
  1859.     // duplicate that here.
  1860. {
  1861.     CloseOpenTransport();
  1862.     CleanupLibraryManager();
  1863.     TermPreferences();
  1864.     
  1865.     OTAssert("TermApplication: Quitting with raw streams open!!!", ! LoggingActive() );
  1866. }
  1867.  
  1868. #if GENERATINGCFM
  1869.  
  1870.     // Raw streams are not tracked by Open Transport.  When the
  1871.     // application quits abnormally, we have to make sure that
  1872.     // we have closed the stream, otherwise it will dangle around
  1873.     // in memory. This would not be too bad except:
  1874.     //
  1875.     // a) the notifier may be called, calling code that is no
  1876.     //    no longer loaded, and
  1877.     // b) the log driver won't let another client hook up as
  1878.     //    the stream logger as long as the stream is open.
  1879.     //
  1880.     // So we have a CFM terminate procedure that shuts down
  1881.     // the raw stream if it's open.
  1882.     //
  1883.     // Note that I don't recommend this approach for closing
  1884.     // endpoints and such.  OT has automatic shutdown code,
  1885.     // and I recommend that you rely on it for emergency shutdown
  1886.     // in the general case.  This code is only necessary because
  1887.     // we're using raw streams.
  1888.     //
  1889.     // Of course, for normal shutdown, ie the user chooses Quit, you
  1890.     // should not rely on OT's emergency shutdown support; you
  1891.     // should always call CloseOpenTransport yourself for normal
  1892.     // shutdown.
  1893.  
  1894.     extern pascal void EmergencyShutdown(void);
  1895.  
  1896.     extern pascal void EmergencyShutdown(void)
  1897.     {
  1898.         if ( LoggingActive() ) {
  1899.             OTDebugStr("EmergencyShutdown: Had to StopLogging");
  1900.             StopLogging();
  1901.         }
  1902.     }
  1903.  
  1904. #else
  1905.  
  1906.     // I could do the equivalent for the 68K case but it's not as
  1907.     // easy and I don't expect there to be many people using this
  1908.     // application on a 68K.
  1909.  
  1910. #endif
  1911.  
  1912. /////////////////////////////////////////////////////////////////////
  1913. #pragma mark **** The Main Line! *****
  1914.  
  1915. extern void main(void)
  1916. {
  1917.     OSStatus err;
  1918.  
  1919.     // We make lots of calls to the OT raw streams API, which
  1920.     // is not available to emulated code on the PPC.  So if we're
  1921.     // generating 68K code, we insert a check to prevent us
  1922.     // running emulated.
  1923.     
  1924.     #if ! GENERATINGCFM
  1925.         {
  1926.             SInt32 response;
  1927.             
  1928.             if ( Gestalt(gestaltSysArchitecture, &response) == noErr &&
  1929.                     response == gestaltPowerPC ) {
  1930.                 OTDebugStr("main: No sneaky running 68K on PPC!");
  1931.                 ExitToShell();
  1932.             }
  1933.         }
  1934.     #endif
  1935.     
  1936.     InitMacToolbox();
  1937.     err = InitApplication();
  1938.     OTAssert("main: InitApplication returned an error", err == noErr);
  1939.     if (err == noErr) {
  1940.     
  1941.         MainEventLoop();
  1942.         
  1943.         TermApplication();
  1944.     }
  1945. }
  1946.